package com.oauthorization.server.config.oauth2;

import com.alibaba.fastjson.JSON;
import com.oauthorization.server.config.redis.RedisSecurityContextRepository;
import com.oauthorization.server.contants.SecurityConstants;
import com.oauthorization.server.entity.BaseResult;
import com.oauthorization.server.utils.SecurityUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.filter.CorsFilter;

import javax.sql.DataSource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
@Order(20)
@Slf4j
public class SecurityConfig {
    @Autowired
    private RedisSecurityContextRepository redisSecurityContextRepository;
    @Autowired
    private CorsFilter corsFilter;
    @Resource
    private DataSource dataSource;

    /**
     * 配置认证相关的过滤器链
     *
     * @param http spring security核心配置类
     * @return 过滤器链
     * @throws Exception 抛出
     */
    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        // 添加跨域过滤器
        http.addFilter(corsFilter);
        // 禁用 csrf 与 cors
        http.csrf(AbstractHttpConfigurer::disable);
        http.cors(AbstractHttpConfigurer::disable);
        //关闭iframe请求
        http.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(frameOptionsConfig -> frameOptionsConfig.disable()));
        //禁用缓存
        http.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.cacheControl(cacheControlConfig -> cacheControlConfig.disable()));
        //不适用session
       // http.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.ALWAYS));

        http.authorizeHttpRequests((authorize) -> authorize
                // 放行静态资源
                .requestMatchers("/assets/**", "/webjars/**", "/static/**", "/favicon.ico",
                        "/login", "/index", "/sys/role/menus/**", "/captcha/**", "/error",
                        "/*.ico", "/*.eot", "/*.svg", "/*.ttf", "/*.woff", "/*.js", "*.js",
                        "/*.css", "/*.map", "/*.html", "/*.jpg", "/*.png", "/rpc/**","/api/**").permitAll()
                .anyRequest().authenticated()
        );
        // 指定登录页面
        http.formLogin(formLogin -> {
                    formLogin.loginPage("/login");
                    formLogin.defaultSuccessUrl("/index");
//                    if (UrlUtils.isAbsoluteUrl(LOGIN_URL)) {
//                        // 绝对路径代表是前后端分离，登录成功和失败改为写回json，不重定向了
//                        formLogin.successHandler(new LoginSuccessHandler());
//                        formLogin.failureHandler(new LoginFailureHandler());
//                    }
                }
        );

        // 退出
        http.logout(logout -> {
            logout.clearAuthentication(true);
            logout.invalidateHttpSession(true);
            logout.deleteCookies();
            logout.logoutSuccessHandler(new LogoutSuccessHandler() {
                @Override
                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                    // 根据JSESSIONID删除ecurityContext
                    redisSecurityContextRepository.clearContext(request);
                    String redirectUri = request.getParameter("redirect_uri");
                    // 重定向到redirect_uri
                    response.sendRedirect(redirectUri);
                }
            });
        });
//

        // 添加BearerTokenAuthenticationFilter，将认证服务当做一个资源服务，解析请求头中的token
        http.oauth2ResourceServer((resourceServer) -> resourceServer
                .jwt(Customizer.withDefaults())
                .accessDeniedHandler(SecurityUtils::exceptionHandler)
                .authenticationEntryPoint(SecurityUtils::exceptionHandler)
        );

        // 作为资源服务器使用
        http.oauth2ResourceServer(oAuth2ResourceServerSpec -> oAuth2ResourceServerSpec
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
                        // 访问服务，没有携带 Authentication 时调用
                        .authenticationEntryPoint((request, response, authException) -> {

                            log.error("进入authenticationEntryPoint============={}", authException);
                            BaseResult<String> result = BaseResult.unauthorized();
                            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                            response.setStatus(BaseResult.ResultCodeEnum.RESULT_CODE_UNAUTHORIZED.getCode());
                            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                            response.getWriter().write(JSON.toJSONString(result));
                            response.getWriter().flush();
                        })
                        .accessDeniedHandler((request, response, accessDeniedException) -> {
                            log.info("accessDeniedHandler=============");
                            BaseResult<String> result = BaseResult.unauthorized();
                            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                            response.setStatus(BaseResult.ResultCodeEnum.RESULT_CODE_UNAUTHORIZED.getCode());
                            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                            response.getWriter().write(JSON.toJSONString(result));
                            response.getWriter().flush();
                        })

                // 访问服务，如果携带了 Authentication,但是用户验证没通过时调用
//                .authenticationFailureHandler((exchange, exception) -> {
//                    return Mono.defer(() -> Mono.just(exchange.getExchange().getResponse()))
//                            .flatMap(response ->
//                                    ResponseUtil.response(response, BaseResult.unauthorized())
//                            );
//                })
        );

//        // 兼容前后端分离与不分离配置
////        if (UrlUtils.isAbsoluteUrl(LOGIN_URL)) {
//            http
//                    // 当未登录时访问认证端点时重定向至login页面
//                    .exceptionHandling((exceptions) -> exceptions
//                            .defaultAuthenticationEntryPointFor(
//                                    new LoginTargetAuthenticationEntryPoint(LOGIN_URL),
//                                    new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
//                            )
//                    );
////        }
//
//        http.exceptionHandling(exceptionHandlingCustomizer -> exceptionHandlingCustomizer.accessDeniedHandler(new AccessDeniedHandler() {
//            @Override
//            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//                System.out.println("==========================http.exceptionHandling(exceptionHandlingCustomizer -> =================================");
//            }
//        }).authenticationEntryPoint(new AuthenticationEntryPoint() {
//            @Override
//            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//                System.out.println("==========================http.authenticationEntryPoint(exceptionHandlingCustomizer -> =================================");
//                BaseResult<String> result = BaseResult.unauthorized();
//                response.setCharacterEncoding(StandardCharsets.UTF_8.name());
//                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
//                response.getWriter().write(JSONUtil.INSTANCE.object2JSON(result));
//                response.getWriter().flush();
//            }
//        }));

        // 使用redis存储、读取登录的认证信息
        http.securityContext(context -> context.securityContextRepository(redisSecurityContextRepository));
        return http.build();
    }

    /**
     * 自定义jwt解析器，设置解析出来的权限信息的前缀与在jwt中的key
     *
     * @return jwt解析器 JwtAuthenticationConverter
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        // 设置解析权限信息的前缀，设置为空是去掉前缀
        grantedAuthoritiesConverter.setAuthorityPrefix("");
        // 设置权限信息在jwt claims中的key
        grantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES_KEY);

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }



    @Bean
    UserDetailsManager userDetailsManager() {
        return new JdbcUserDetailsManager(dataSource);
    }
}
